#ifndef _ES_C_
#define _ES_C_

/* $Id: es.c,v 1.1 2000/03/01 14:09:09 bobby Exp bobby $
 * ----
 * $Revision: 1.1 $
 */

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/time.h>
#include <string.h>
#include <signal.h>
#include <time.h>

#include "queue.h"
#include "common.h"
#include "es.h"
#include "ls.h"
#include "rt.h"
#include "n2h.h"
#include "luke.h"

static struct el* g_el;
static int nextSet = 1; /* True */
static int verb = 0;

int init_new_el()
{
	InitDQ(g_el, struct el);
	assert (g_el);

	g_el->es_head = 0x0;
	return (g_el != 0x0);
}

void add_new_es()
{
	struct es* n_es;
	struct el* n_el = (struct el*)
		getmem (sizeof(struct el));

	struct el* tail = g_el->prev;
	InsertDQ(tail, n_el);

	// added new es to tail
	// lets start a new queue here
  
	{
		struct es* nhead = tail->es_head;
		InitDQ(nhead, struct es);

		tail = g_el->prev;     
		tail->es_head = nhead; 

		n_es = nhead;

		n_es->ev = _es_null;
		n_es->peer0 = n_es->peer1 = 
			n_es->port0 = n_es->port1 =
			n_es->cost = -1;
		n_es->name = 0x0;
	}
}

void add_to_last_es(e_type ev,
	node peer0, int port0,
	node peer1, int port1,
	int cost, char *name)
{
	struct el* tail = g_el->prev;
	bool local_event = false;
  
	assert (tail->es_head);

	// check for re-defined link (for establish)
	// check for local event (for tear-down, update)
	switch (ev) {
		case _es_link:
			// a local event?
			if ((peer0 == get_myid()) || peer1 == get_myid())
				local_event = true;
			break;
		case _ud_link:
			// a local event?
			if (geteventbylink(name))
				local_event = true;
			break;
		case _td_link:
			// a local event?
			if (geteventbylink(name))
				local_event = true;
			break;
		default:
			printf("[es]\t\tUnknown event!\n");
			break;
	}

	if (!local_event) {
		printf("[es]\t Not a local event, skip\n");
		return;
	}

	printf("[es]\t Adding into local event\n");

	{
		struct es* es_tail = (tail->es_head)->prev;
    
		struct es* n_es = (struct es*)
			getmem (sizeof(struct es));
    
		n_es->ev = ev;
		n_es->peer0 = peer0;
		n_es->port0 = port0;
		n_es->peer1 = peer1;
		n_es->port1 = port1;
		n_es->cost = cost;
		n_es->name = (char *)
			getmem(strlen(name)+1);
		strcpy (n_es->name, name);

		InsertDQ (es_tail, n_es);
	}
}

void catch_alarm(int sig)
{
    nextSet = 1;                /* Set nextSet to True */
    signal(sig, catch_alarm);   /* Re-register signal handler */
}

/*
 * A simple walk of event sets: dispatch and print a event SET every 2 sec
 */
void walk_el(int update_time, int time_between, int verbose)
{
	struct el *el;
	struct es *es_hd;
	struct es *es;
    time_t start_time, curr_time;
    int idx;
    verb = verbose; /* global variable */

	assert (g_el->next);
	assert (get_myid >= 0);
    
	print_el();

	/* initialize link set, routing table, and routing table */
	create_ls();
	create_rt();
	init_rt_from_n2h();
	
    /* Establish a handler for signal alarms */
    signal (SIGALRM, catch_alarm);

    el = g_el->next;
    assert(g_el->next);
    do {
        if (nextSet == 1) {
            nextSet = 0;
            assert(el);
            /* Update local link state */
            es_hd = el->es_head;
            assert (es_hd);
            /*printf("[es] >>>>>>>>>> Dispatch next event set <<<<<<<<<<<<<\n");*/
            for (es=es_hd->next ; es!=es_hd ; es=es->next) {
                /*printf("[es] Dispatching next event ... \n");*/
                print_event(es); /* Log each event in the event set that is acted upon */
                dispatch_event(es);
            }
            alarm(time_between);

            /*printf("[es] >>>>>>> Start dumping data stuctures <<<<<<<<<<<\n");
            print_n2h();
            print_ls();*/
            print_rt(); /* After each event set, print the routing table */
            el = el->next;
        }
        /* Run distance vector protocol in a loop for update_time
         * seconds, and then send out an update to all neighbors */
        time(&start_time);
        time(&curr_time);
        while( difftime(curr_time, start_time) < update_time ) {
            dvp(verb);
            time (&curr_time);
        }
        update_rt_from_ls();
        send_rt();
    } while (el != g_el);
    /* Send 5 (arbitrary) more updates after the last event set has been executed */
    for (idx = 0; idx < 5; ++idx) {
        time(&start_time);
        time(&curr_time);
        while( difftime(curr_time, start_time) < update_time ) {
            dvp(verb);
            if(verb)
                print_rt();
            time (&curr_time);
        }
        update_rt_from_ls();
        send_rt();
    }
    /* Print the routing table one final time */
    print_rt();
}

/*
 * -------------------------------------
 * Dispatch one event
 * -------------------------------------
 */
void dispatch_event(struct es* es)
{
    struct rte *rt, *g_rt, *peer_rt;
    node me, peer;
    int prev_cost;
    struct link *lk;
	assert(es);

    me = get_myid();
    g_rt = get_g_rt();
    assert(g_rt->next);

	switch (es->ev) {
		case _es_link:
			add_link(es->peer0, es->port0, es->peer1, es->port1,
				es->cost, es->name);
            if (es->peer0 == me) {
                peer = es->peer1;
            } else if (es->peer1 == me) {
                peer = es->peer0;
            } else {
                /* This should not happen */
                fprintf(stderr, "Local node is not part of this link\n");
                exit(EXIT_FAILURE);
            }

            /* Update routing table */
            peer_rt = find_rte(peer);
            if (!peer_rt) {
                /* If there is no entry in the routing table for
                 * the node connected by this link, add one */
                add_rte(peer, es->cost, peer);
            } else {
                /* Check if this link provides a lower cost than the current route. If it does, update the route */
                if (es->cost < peer_rt->c || peer_rt->c == INFINITY) {
                    update_rte(peer, es->cost, peer);
                }
            }
			break;
		case _ud_link:
			ud_link(es->name, es->cost);
            lk = find_link(es->name);
            if (lk->peer0 == me) {
                peer = lk->peer1;
            } else if (lk->peer1 == me) {
                peer = lk->peer0;
            } else {
                /* This should not happen */
                fprintf(stderr, "Local node is not part of this link\n");
                exit(EXIT_FAILURE);
            }

            peer_rt = find_rte(peer);
            prev_cost = peer_rt->c; 

            if (peer_rt->nh == peer || es->cost < peer_rt->c || peer_rt->c == INFINITY) {
                printf("Updated route: \tNode %d  Cost %d NextHop %d\n", peer, es->cost, peer);
                update_rte(peer, es->cost, peer);
                /* If a link is updated, for each route rt in the routing table,
                 * check to see if the next hop is the node whose link was updated.
                 * Set the cost of each of these routes appropriately. */
                rt = g_rt->next;
                while (rt != g_rt) {
                    if (rt->nh == peer && rt->d != peer && rt->c != INFINITY) {
                        if(DEBUG) printf("rt->c + es->cost - prev_cost: %d + %d - %d = %d\n\n", rt->c, es->cost, prev_cost, rt->c + es->cost - prev_cost);
                        printf("Updated route: \tNode %d  Cost %d NextHop %d\n", rt->d, rt->c + es->cost - prev_cost, peer);
                        update_rte(rt->d, rt->c + es->cost - prev_cost, peer);
                    }
                    rt = rt->next;
                }
            }
			break;
		case _td_link:
            lk = find_link(es->name);
            if (lk->peer0 == me) {
                peer = lk->peer1;
            } else if (lk->peer1 == me) {
                peer = lk->peer0;
            } else {
                /* This should not happen */
                fprintf(stderr, "Local node is not part of this link\n");
                exit(EXIT_FAILURE);
            }

            /* If a link is broken, for each route rt in the routing table,
             * check to see if the next hop is the node whose link was broken.
             * Set the cost of each of these routes to infinity. */
            rt = g_rt->next;
            while (rt != g_rt) {
                if (rt->nh == peer) {
                    update_rte(rt->d, INFINITY, peer);
                }
                rt = rt->next;
            }
			del_link(es->name);
			break;
		default:
			printf("[es]\t\tUnknown event!\n");
			break;
	}
}

/*
 * print out the whole event LIST
 */
void print_el()
{
	struct el *el;
	struct es *es_hd;
	struct es *es;

	assert (g_el->next);

	printf("\n\n");
	printf("[es] >>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<\n");
	printf("[es] >>>>>>>>>> Dumping all event sets  <<<<<<<<<<<<<\n");
	printf("[es] >>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<\n");

	for (el = g_el->next ; el != g_el ; el = el->next) {

		assert(el);
		es_hd = el->es_head;
		assert (es_hd);
	
		printf("\n[es] ***** Dumping next event set *****\n");

		for (es=es_hd->next ; es!=es_hd ; es=es->next)
			print_event(es);
	}     
}

/*
 * print out one event: establish, update, or, teardown
 */
void print_event(struct es* es)
{
	assert(es);

	switch (es->ev) {
		case _es_null:
			printf("[es]\t----- NULL event -----\n");
			break;
		case _es_link:
			printf("[es]\t----- Establish event -----\n");
			break;
		case _ud_link:
			printf("[es]\t----- Update event -----\n");
			break;
		case _td_link:
			printf("[es]\t----- Teardown event -----\n");
			break;
		default:
			printf("[es]\t----- Unknown event-----\n");
			break;
	}
	printf("[es]\t link-name(%s)\n",es->name);
	printf("[es]\t node(%d)port(%d) <--> node(%d)port(%d)\n", 
		es->peer0,es->port0,es->peer1,es->port1);
	printf("[es]\t cost(%d)\n", es->cost);
}

struct es *geteventbylink(char *lname)
{
	struct el *el;
	struct es *es_hd;
	struct es *es;

	assert (g_el->next);
	assert (lname);

	for (el = g_el->next ; el != g_el ; el = el->next) {

		assert(el);
		es_hd = el->es_head;
		assert (es_hd);
	
		for (es=es_hd->next ; es!=es_hd ; es=es->next)
			if (!strcmp(lname, es->name))
				return es;
	}
	return 0x0;
}

#endif

